﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using FluentlyXml.Core.Deserialization;
using FluentlyXml.Core.Serialization;
using FluentlyXml.Core.Utility;

namespace FluentlyXml.Core
{
	internal class FluentSerializationConfig : ISerializationConfig, IDeserializationConfig
	{
		private readonly IDictionary<Type, TypeSerializationConfig> _typeSerializationConfig = new Dictionary<Type, TypeSerializationConfig>();
		private readonly IDictionary<Type, TypeDeserializationConfig> _typeDeserializationConfig = new Dictionary<Type, TypeDeserializationConfig>();

		#region ISerializationConfig

		private TypeSerializationConfig GetTypeSerializationConfigFor(Type type)
		{
			if (!_typeSerializationConfig.ContainsKey(type))
			{
				_typeSerializationConfig.Add(type, new TypeSerializationConfig(type));
			}

			return _typeSerializationConfig[type];
		}

		public void SerializePropertyAsArray(Type target, PropertyInfo property)
		{
			var config = GetTypeSerializationConfigFor(target);
			config.SerializePropertyAsArray(property);
		}

		public void SetIgnoredProperty(Type target, PropertyInfo property)
		{
			var config = GetTypeSerializationConfigFor(target);
			config.IgnoreProperty(property);
		}

		public void MarkTypeAsSealed(Type type)
		{
			var config = GetTypeSerializationConfigFor(type);
			config.SerializeAllDerivedTypesUsingThisConfig();
		}

		public void SerializePropertyAsElement(Type target, PropertyInfo property, string elementName)
		{
			var config = GetTypeSerializationConfigFor(target);
			config.SerializePropertyAsElement(property, elementName);
		}

		public void SerializePropertyAsElementUsing(Type target, PropertyInfo property, string elementName, Func<object, object> converter)
		{
			var config = GetTypeSerializationConfigFor(target);
			config.SerializePropertyAsElementUsing(property, elementName, converter);
		}

		#endregion

		#region Deserialization

		private TypeDeserializationConfig GetTypeDeserializationConfigFor(Type type)
		{
			if (!_typeDeserializationConfig.ContainsKey(type))
			{
				_typeDeserializationConfig.Add(type, new TypeDeserializationConfig(type));
			}

			return _typeDeserializationConfig[type];
		}

		public void SetIdentityFunction(Type target, Func<object, object> selector)
		{
			var config = GetTypeDeserializationConfigFor(target);
			config.SetIdentityFunction(selector);
		}

		public void SetPropertyDeserializer(Type target, PropertyInfo property, PropertyDeserializer propertyDeserializer)
		{
			var config = GetTypeDeserializationConfigFor(target);
			config.AddPropertyDeserializer(property, propertyDeserializer);
		}

		public void AddPostDeserializationCallback(Type target, Action<object> callback)
		{
			var config = GetTypeDeserializationConfigFor(target);
			config.AddPostDeserializationCallback(callback);
		}

		#endregion

		public void MergeFrom(FluentSerializationConfig config)
		{
			var typeSerializers = config._typeSerializationConfig.Values;

			//Merge any serializers that are defined in both places
			typeSerializers.Where(t => _typeSerializationConfig.ContainsKey(t.AppliesToType))
				.ForEach(t => _typeSerializationConfig[t.AppliesToType].MergeWith(t));

			//Add any new type serializers
			typeSerializers.Where(t => !_typeSerializationConfig.ContainsKey(t.AppliesToType))
				.ForEach(t => _typeSerializationConfig.Add(t.AppliesToType, t));

			var typeDeserializers = config._typeDeserializationConfig.Values;

			//Merge any deserializers that are defined in both places
			typeDeserializers.Where(t => _typeDeserializationConfig.ContainsKey(t.AppliesToType))
				.ForEach(t => _typeDeserializationConfig[t.AppliesToType].MergeWith(t));

			//Add any new type deserializers
			typeDeserializers.Where(t => !_typeDeserializationConfig.ContainsKey(t.AppliesToType))
				.ForEach(t => _typeDeserializationConfig.Add(t.AppliesToType, t));
		}


		public RuntimeSerializationConfig Compile()
		{
			var runtimeConfig = new RuntimeSerializationConfig();

			foreach (var serializerConfig in _typeSerializationConfig.Values)
			{
				var serializer = serializerConfig.BuildSerializer();
				runtimeConfig.AddSerializer(serializer);
			}

			foreach (var deserializerConfig in _typeDeserializationConfig.Values)
			{
				var deserializer = deserializerConfig.BuildDeserializer();
				runtimeConfig.AddDeserializer(deserializer);
			}

			return runtimeConfig;
		}

	}
}